package com.travelgroup.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.travelgroup.constant.RedisConstant;
import com.travelgroup.domain.dto.ActivityDTO;
import com.travelgroup.domain.dto.ScheduleDTO;
import com.travelgroup.domain.entity.Activity;
import com.travelgroup.domain.entity.Location;
import com.travelgroup.domain.entity.Schedule;
import com.travelgroup.domain.vo.ActivityVO;
import com.travelgroup.domain.vo.ItineraryVO;
import com.travelgroup.domain.vo.ScheduleVO;
import com.travelgroup.enums.ActivityType;
import com.travelgroup.exception.BusinessException;
import com.travelgroup.exception.R;
import com.travelgroup.mapper.ScheduleMapper;
import com.travelgroup.service.IActivityService;
import com.travelgroup.service.ILocationService;
import com.travelgroup.service.IScheduleService;
import com.travelgroup.utils.RedisUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ScheduleServiceImpl extends ServiceImpl<ScheduleMapper, Schedule>
        implements IScheduleService {
    @Resource
    private IActivityService activityService;
    @Resource
    private RedisUtil redisUtil;
    @Resource
    private ILocationService locationService;

    /**
     * 保存行程信息
     * @param scheduleDTOs
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void saveSchedules(List<ScheduleDTO> scheduleDTOs) {
        // 保存每日行程
        if (scheduleDTOs == null || scheduleDTOs.isEmpty()) throw new BusinessException("每日行程不能为空");
        List<Schedule> schedules = scheduleDTOs.stream()
                .map(s -> BeanUtil.copyProperties(s, Schedule.class))
                .toList();
        if (!this.saveBatch(schedules)) throw new BusinessException("每日行程存储失败");
        // 删除每日行程缓存
        redisUtil.removeById(RedisConstant.SCHEDULE_KEY, schedules.getFirst().getItineraryId());

        // 保存每日行程的活动
        List<Activity> allActivities = new ArrayList<>();
        for (int i = 0; i < scheduleDTOs.size(); i++) {
            // 为每个活动关联每日行程
            List<ActivityDTO> activitiesDTO = scheduleDTOs.get(i).getActivities();
            if (activitiesDTO == null || activitiesDTO.isEmpty()) continue;
            List<Activity> activities = activitiesDTO.stream()
                    .map(a -> BeanUtil.copyProperties(a, Activity.class))
                    .toList();
            Long id = schedules.get(i).getId();
            activities.forEach(a -> a.setScheduleId(id));
            allActivities.addAll(activities);
            redisUtil.removeById(RedisConstant.ACTIVITY_KEY, id);
        }
        if (!allActivities.isEmpty() && !this.activityService.saveBatch(allActivities)) {
            throw new BusinessException("行程活动存储失败");
        }
    }

    /**
     * 获取行程总结
     * @param id
     * @param itineraryVO
     */
    @Override
    public void queryItinerarySummery(Long id, ItineraryVO itineraryVO) {
        // 获取全部行程
        List<Schedule> schedules = getSchedulesByRedis(id);

        if (schedules == null || schedules.isEmpty()) throw new BusinessException("数据库信息错误");

        // 获取全部活动信息
        Map<Long, List<Activity>> activitiesByScheduleId  = getActivitiesByScheduleId(schedules);

        // 再将全部活动拆分成每天的活动并进行统计
        double shoppingHour = 0;
        double freeHour = 0;
        for (Schedule schedule : schedules) {

            // 获取当天的活动
            List<Activity> activityList = activitiesByScheduleId.getOrDefault(schedule.getId(), Collections.emptyList());

            // 景点/场馆数量
            itineraryVO.setAttractionCount(
                    itineraryVO.getAttractionCount() +
                    Long.valueOf(
                            activityList.stream()
                                    .filter(a -> a.getActivityType() == ActivityType.ATTRACTION)
                                    .count()
                    ).intValue());

            // 酒店数量
            itineraryVO.setHotelCount(
                    itineraryVO.getHotelCount() +
                    Long.valueOf(
                            activityList.stream()
                                    .filter(a -> a.getActivityType() == ActivityType.HOTEL)
                                    .count()
                    ).intValue());

            // 统计自由活动信息
            List<Activity> freeActivities = activityList.stream()
                    .filter(a -> a.getActivityType() == ActivityType.FREE)
                    .toList();
            itineraryVO.setFreeCount(itineraryVO.getFreeCount() + freeActivities.size());
            freeHour += freeActivities.stream()
                    .mapToDouble(a -> Duration.between(a.getStartTime().toInstant(),
                            a.getEndTime().toInstant()).toHours() * 60
                            + Duration.between(a.getStartTime().toInstant(),
                            a.getEndTime().toInstant()).toMinutesPart())
                    .sum();

            // 统计购物信息
            List<Activity> shoppingActivities = activityList.stream()
                    .filter(a -> a.getActivityType() == ActivityType.SHOPPING)
                    .toList();
            itineraryVO.setShoppingCount(itineraryVO.getShoppingCount() + shoppingActivities.size());
            shoppingHour += shoppingActivities.stream()
                    .mapToDouble(a -> Duration.between(a.getStartTime().toInstant(),
                            a.getEndTime().toInstant()).toHours() * 60
                            + Duration.between(a.getStartTime().toInstant(),
                            a.getEndTime().toInstant()).toMinutesPart())
                    .sum();

            // 早午晚餐数量
            if (activityList.stream()
                    .anyMatch(a -> a.getActivityType() == ActivityType.BREAKFAST)) {
                itineraryVO.setBreakfastCount(itineraryVO.getBreakfastCount() + 1);
            } else {
                // 当天没有早餐需要自理
                itineraryVO.setSelfCount(itineraryVO.getSelfCount() + 1);
            }

            if (activityList.stream()
                    .anyMatch(a -> a.getActivityType() == ActivityType.LUNCH)) {
                itineraryVO.setLunchCount(itineraryVO.getLunchCount() + 1);
            } else {
                itineraryVO.setSelfCount(itineraryVO.getSelfCount() + 1);
            }

            if (activityList.stream()
                    .anyMatch(a -> a.getActivityType() == ActivityType.DINNER)) {
                itineraryVO.setDinnerCount(itineraryVO.getDinnerCount() + 1);
            } else {
                itineraryVO.setSelfCount(itineraryVO.getSelfCount() + 1);
            }
        }
        int freeSeconds = (int) freeHour * 60;
        int shoppingSeconds = (int) shoppingHour * 60;
        itineraryVO.setFreeTime(String.format("%d小时 %d分钟", freeSeconds / 3600, (freeSeconds % 3600) / 60));
        itineraryVO.setShoppingTime(String.format("%d小时 %d分钟", shoppingSeconds / 3600, (shoppingSeconds % 3600) / 60));
    }

    /**
     * 删除行程
     * @param itineraryId
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void deleteSchedule(Long itineraryId) {
        // 获取全部行程
        List<Schedule> schedules = getSchedulesByRedis(itineraryId);
        if (schedules == null || schedules.isEmpty()) {
            throw new BusinessException("数据库数据错误");
        }

        // 获取所有活动
        List<Activity> allActivities = new ArrayList<>();
        for (Schedule schedule : schedules) {
            Long scheduleId = schedule.getId();
            List<Activity> activities =  activityService.getActivitiesByRedis(scheduleId);
            allActivities.addAll(activities);
        }

        // 删除行程和活动以及缓存信息
        if (!this.removeBatchByIds(schedules)) {
            throw new BusinessException("行程信息删除失败");
        }
        if (!activityService.removeBatchByIds(allActivities)) {
            throw new BusinessException("行程活动删除失败");
        }
        redisUtil.removeById(RedisConstant.SCHEDULE_KEY, itineraryId);
        for (Schedule schedule : schedules) {
            Long scheduleId = schedule.getId();
            redisUtil.removeById(RedisConstant.ACTIVITY_KEY, scheduleId);
        }
    }

    /**
     * 更新行程
     * @param id
     * @param scheduleDTO
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public R<ScheduleDTO> updateSchedule(Long id, ScheduleDTO scheduleDTO) {
        // 获取行程信息
        Schedule schedule = this.getById(id);
        if (schedule == null) throw new BusinessException("行程信息不存在");

        // 更新行程信息
        BeanUtil.copyProperties(scheduleDTO, schedule);
        if (!this.updateById(schedule)) {
            throw new BusinessException("行程信息修改失败");
        }

        // 获取活动信息
        List<Activity> activities =  activityService.getActivitiesByRedis(id);
        if (activities == null || activities.isEmpty()) {
            return R.success("行程信息修改成功", scheduleDTO);
        }

        // 获取当日活动信息
        List<ActivityDTO> activityDTOS = scheduleDTO.getActivities();
        Map<Long, ActivityDTO> dtoMap = activityDTOS.stream()
                .filter(dto -> dto.getId() != null)
                .collect(Collectors.toMap(ActivityDTO::getId, Function.identity()));

        // 更新当日活动信息
        for (Activity activity : activities) {
            ActivityDTO dto = dtoMap.get(activity.getId());
            if (dto != null) {
                BeanUtil.copyProperties(dto, activity);
            } else {
                // 如果存在被删除的活动信息
                if (!activityService.removeById(activity.getId())) {
                    throw new BusinessException("删除行程信息失败");
                }
            }
        }

        // 获取id为空的数据作为新增活动进行保存
        List<Activity> activityList = activityDTOS.stream()
                .filter(dto -> dto.getId() == null)
                .map(dto -> BeanUtil.copyProperties(dto, Activity.class)).toList();
        if (!activityList.isEmpty()) {
            activityList.forEach(activity -> activity.setScheduleId(id));
            if (!activityService.saveBatch(activityList)) throw new BusinessException("行程信息保存失败");
        }

        // 更新行程
        if (!activityService.updateBatchById(activities)) throw new BusinessException("行程信息修改失败");
        redisUtil.removeById(RedisConstant.ACTIVITY_KEY, id);
        return R.success("行程信息修改成功", scheduleDTO);
    }

    /**
     * 根据总行程id查询全部行程
     * @param id
     * @return
     */
    @Override
    public R<List<ScheduleVO>> queryScheduleByIds(Long id) {
        // 获取行程信息
        List<Schedule> schedules = getSchedulesByRedis(id);

        // 转换为VO
        List<ScheduleVO> scheduleVOS = queryLocationForSchedule(schedules);

        return R.success("查询行程信息成功", scheduleVOS);
    }

    /**
     * 根据行程id查询当日活动
     * @param schedules
     * @return
     */
    private Map<Long, List<Activity>> getActivitiesByScheduleId(List<Schedule> schedules) {
        // 将数据按照行程id存入Map中
        Map<Long, List<Activity>> activitiesByScheduleId = new HashMap<>();
        for (Schedule schedule : schedules) {
            Long scheduleId = schedule.getId();
            List<Activity> activities = activityService.getActivitiesByRedis(scheduleId);
            activitiesByScheduleId.put(scheduleId, activities);
        }
        return activitiesByScheduleId;
    }

    /**
     * 根据id删除行程
     * @param itineraryId
     * @param id
     * @return
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public R<?> deleteScheduleById(Long itineraryId, Long id) {
        // 获取按日期排序的日程
        List<Schedule> schedules = getSchedulesByRedis(itineraryId);

        // 获取需要删除的目标日程
        Schedule schedule = schedules.stream().filter(s -> s.getId().equals(id)).findFirst().orElse(null);
        if (schedule == null) throw new BusinessException("日程信息不存在");
        Integer dayNumber = schedule.getDayNumber();

        // 将排在目标日程后的所有日程天数减一
        schedules.forEach(s -> {
            if (dayNumber > s.getDayNumber()) {
                s.setDayNumber(s.getDayNumber() - 1);
            }
        });

        // 删除日程以及当日活动
        if (!activityService.lambdaUpdate().eq(Activity::getScheduleId, id).remove()) {
            throw new BusinessException("日程活动删除失败");
        }
        if (!this.removeById(id)) {
            throw new BusinessException("日程删除失败");
        }
        schedules.remove(schedule);

        if (!updateBatchById(schedules)) {
            throw new BusinessException("日程信息更新失败");
        }
        redisUtil.removeById(RedisConstant.ACTIVITY_KEY, id);
        redisUtil.removeById(RedisConstant.SCHEDULE_KEY, itineraryId);
        return null;
    }

    /**
     * 获取行程前往地点
     * @param schedules
     * @return
     */
    private List<ScheduleVO> queryLocationForSchedule(List<Schedule> schedules) {
        // 根据行程id获取全部活动
        List<ScheduleVO> scheduleVOS = new ArrayList<>();
        Map<Long, List<Activity>> activitiesByScheduleId = getActivitiesByScheduleId(schedules);
        for (Schedule schedule : schedules) {
            // 获取地点信息
            Long scheduleId = schedule.getId();
            ScheduleVO scheduleVO = new ScheduleVO();
            BeanUtil.copyProperties(schedule, scheduleVO);
            // 设置住宿信息
            Long locationId = schedule.getAccommodationId();
            Location location = locationService.getByRedis(locationId);
            scheduleVO.setAccommodation(location);

            // 为活动设置地点信息
            List<Activity> activities = activitiesByScheduleId.get(scheduleId);
            List<ActivityVO> activityVOS = activities.stream()
                    .filter(a -> scheduleId.equals(a.getScheduleId()))
                    .map(a -> BeanUtil.copyProperties(a, ActivityVO.class))
                    .toList();
            activityVOS.forEach(
                    vo -> vo.setLocation(
                            redisUtil.queryById(RedisConstant.LOCATION_KEY,
                                    vo.getLocationId(), Location.class,
                                    locationService::getById,
                                    RedisConstant.CACHE_TTL, TimeUnit.MINUTES)));
            scheduleVO.setActivities(activityVOS);
            scheduleVOS.add(scheduleVO);
        }
        return scheduleVOS;
    }

    /**
     * 根据id查询行程
     * @param itineraryId
     * @return
     */
    @Override
    public List<Schedule> getSchedulesByRedis(Long itineraryId) {
        return  redisUtil.queryListById(RedisConstant.SCHEDULE_KEY, itineraryId, Schedule.class,
                i -> this.lambdaQuery()
                        .eq(Schedule::getItineraryId, i)
                        .orderByAsc(Schedule::getDayNumber)
                        .list(), RedisConstant.CACHE_TTL, TimeUnit.MINUTES);
    }
}
